home *** CD-ROM | disk | FTP | other *** search
/ Freelog 100 / FreelogNo100-NovembreDecembre2010.iso / Musique / solfege / solfege-win32-3.17.0.exe / {app} / bin / Lib / SimpleXMLRPCServer.py < prev    next >
Text File  |  2006-06-09  |  22KB  |  596 lines

  1. """Simple XML-RPC Server.
  2.  
  3. This module can be used to create simple XML-RPC servers
  4. by creating a server and either installing functions, a
  5. class instance, or by extending the SimpleXMLRPCServer
  6. class.
  7.  
  8. It can also be used to handle XML-RPC requests in a CGI
  9. environment using CGIXMLRPCRequestHandler.
  10.  
  11. A list of possible usage patterns follows:
  12.  
  13. 1. Install functions:
  14.  
  15. server = SimpleXMLRPCServer(("localhost", 8000))
  16. server.register_function(pow)
  17. server.register_function(lambda x,y: x+y, 'add')
  18. server.serve_forever()
  19.  
  20. 2. Install an instance:
  21.  
  22. class MyFuncs:
  23.     def __init__(self):
  24.         # make all of the string functions available through
  25.         # string.func_name
  26.         import string
  27.         self.string = string
  28.     def _listMethods(self):
  29.         # implement this method so that system.listMethods
  30.         # knows to advertise the strings methods
  31.         return list_public_methods(self) + \
  32.                 ['string.' + method for method in list_public_methods(self.string)]
  33.     def pow(self, x, y): return pow(x, y)
  34.     def add(self, x, y) : return x + y
  35.  
  36. server = SimpleXMLRPCServer(("localhost", 8000))
  37. server.register_introspection_functions()
  38. server.register_instance(MyFuncs())
  39. server.serve_forever()
  40.  
  41. 3. Install an instance with custom dispatch method:
  42.  
  43. class Math:
  44.     def _listMethods(self):
  45.         # this method must be present for system.listMethods
  46.         # to work
  47.         return ['add', 'pow']
  48.     def _methodHelp(self, method):
  49.         # this method must be present for system.methodHelp
  50.         # to work
  51.         if method == 'add':
  52.             return "add(2,3) => 5"
  53.         elif method == 'pow':
  54.             return "pow(x, y[, z]) => number"
  55.         else:
  56.             # By convention, return empty
  57.             # string if no help is available
  58.             return ""
  59.     def _dispatch(self, method, params):
  60.         if method == 'pow':
  61.             return pow(*params)
  62.         elif method == 'add':
  63.             return params[0] + params[1]
  64.         else:
  65.             raise 'bad method'
  66.  
  67. server = SimpleXMLRPCServer(("localhost", 8000))
  68. server.register_introspection_functions()
  69. server.register_instance(Math())
  70. server.serve_forever()
  71.  
  72. 4. Subclass SimpleXMLRPCServer:
  73.  
  74. class MathServer(SimpleXMLRPCServer):
  75.     def _dispatch(self, method, params):
  76.         try:
  77.             # We are forcing the 'export_' prefix on methods that are
  78.             # callable through XML-RPC to prevent potential security
  79.             # problems
  80.             func = getattr(self, 'export_' + method)
  81.         except AttributeError:
  82.             raise Exception('method "%s" is not supported' % method)
  83.         else:
  84.             return func(*params)
  85.  
  86.     def export_add(self, x, y):
  87.         return x + y
  88.  
  89. server = MathServer(("localhost", 8000))
  90. server.serve_forever()
  91.  
  92. 5. CGI script:
  93.  
  94. server = CGIXMLRPCRequestHandler()
  95. server.register_function(pow)
  96. server.handle_request()
  97. """
  98.  
  99. # Written by Brian Quinlan (brian@sweetapp.com).
  100. # Based on code written by Fredrik Lundh.
  101.  
  102. import xmlrpclib
  103. from xmlrpclib import Fault
  104. import SocketServer
  105. import BaseHTTPServer
  106. import sys
  107. import os
  108. try:
  109.     import fcntl
  110. except ImportError:
  111.     fcntl = None
  112.  
  113. def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
  114.     """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
  115.  
  116.     Resolves a dotted attribute name to an object.  Raises
  117.     an AttributeError if any attribute in the chain starts with a '_'.
  118.  
  119.     If the optional allow_dotted_names argument is false, dots are not
  120.     supported and this function operates similar to getattr(obj, attr).
  121.     """
  122.  
  123.     if allow_dotted_names:
  124.         attrs = attr.split('.')
  125.     else:
  126.         attrs = [attr]
  127.  
  128.     for i in attrs:
  129.         if i.startswith('_'):
  130.             raise AttributeError(
  131.                 'attempt to access private attribute "%s"' % i
  132.                 )
  133.         else:
  134.             obj = getattr(obj,i)
  135.     return obj
  136.  
  137. def list_public_methods(obj):
  138.     """Returns a list of attribute strings, found in the specified
  139.     object, which represent callable attributes"""
  140.  
  141.     return [member for member in dir(obj)
  142.                 if not member.startswith('_') and
  143.                     callable(getattr(obj, member))]
  144.  
  145. def remove_duplicates(lst):
  146.     """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
  147.  
  148.     Returns a copy of a list without duplicates. Every list
  149.     item must be hashable and the order of the items in the
  150.     resulting list is not defined.
  151.     """
  152.     u = {}
  153.     for x in lst:
  154.         u[x] = 1
  155.  
  156.     return u.keys()
  157.  
  158. class SimpleXMLRPCDispatcher:
  159.     """Mix-in class that dispatches XML-RPC requests.
  160.  
  161.     This class is used to register XML-RPC method handlers
  162.     and then to dispatch them. There should never be any
  163.     reason to instantiate this class directly.
  164.     """
  165.  
  166.     def __init__(self, allow_none, encoding):
  167.         self.funcs = {}
  168.         self.instance = None
  169.         self.allow_none = allow_none
  170.         self.encoding = encoding
  171.  
  172.     def register_instance(self, instance, allow_dotted_names=False):
  173.         """Registers an instance to respond to XML-RPC requests.
  174.  
  175.         Only one instance can be installed at a time.
  176.  
  177.         If the registered instance has a _dispatch method then that
  178.         method will be called with the name of the XML-RPC method and
  179.         its parameters as a tuple
  180.         e.g. instance._dispatch('add',(2,3))
  181.  
  182.         If the registered instance does not have a _dispatch method
  183.         then the instance will be searched to find a matching method
  184.         and, if found, will be called. Methods beginning with an '_'
  185.         are considered private and will not be called by
  186.         SimpleXMLRPCServer.
  187.  
  188.         If a registered function matches a XML-RPC request, then it
  189.         will be called instead of the registered instance.
  190.  
  191.         If the optional allow_dotted_names argument is true and the
  192.         instance does not have a _dispatch method, method names
  193.         containing dots are supported and resolved, as long as none of
  194.         the name segments start with an '_'.
  195.  
  196.             *** SECURITY WARNING: ***
  197.  
  198.             Enabling the allow_dotted_names options allows intruders
  199.             to access your module's global variables and may allow
  200.             intruders to execute arbitrary code on your machine.  Only
  201.             use this option on a secure, closed network.
  202.  
  203.         """
  204.  
  205.         self.instance = instance
  206.         self.allow_dotted_names = allow_dotted_names
  207.  
  208.     def register_function(self, function, name = None):
  209.         """Registers a function to respond to XML-RPC requests.
  210.  
  211.         The optional name argument can be used to set a Unicode name
  212.         for the function.
  213.         """
  214.  
  215.         if name is None:
  216.             name = function.__name__
  217.         self.funcs[name] = function
  218.  
  219.     def register_introspection_functions(self):
  220.         """Registers the XML-RPC introspection methods in the system
  221.         namespace.
  222.  
  223.         see http://xmlrpc.usefulinc.com/doc/reserved.html
  224.         """
  225.  
  226.         self.funcs.update({'system.listMethods' : self.system_listMethods,
  227.                       'system.methodSignature' : self.system_methodSignature,
  228.                       'system.methodHelp' : self.system_methodHelp})
  229.  
  230.     def register_multicall_functions(self):
  231.         """Registers the XML-RPC multicall method in the system
  232.         namespace.
  233.  
  234.         see http://www.xmlrpc.com/discuss/msgReader$1208"""
  235.  
  236.         self.funcs.update({'system.multicall' : self.system_multicall})
  237.  
  238.     def _marshaled_dispatch(self, data, dispatch_method = None):
  239.         """Dispatches an XML-RPC method from marshalled (XML) data.
  240.  
  241.         XML-RPC methods are dispatched from the marshalled (XML) data
  242.         using the _dispatch method and the result is returned as
  243.         marshalled data. For backwards compatibility, a dispatch
  244.         function can be provided as an argument (see comment in
  245.         SimpleXMLRPCRequestHandler.do_POST) but overriding the
  246.         existing method through subclassing is the prefered means
  247.         of changing method dispatch behavior.
  248.         """
  249.  
  250.         try:
  251.             params, method = xmlrpclib.loads(data)
  252.  
  253.             # generate response
  254.             if dispatch_method is not None:
  255.                 response = dispatch_method(method, params)
  256.             else:
  257.                 response = self._dispatch(method, params)
  258.             # wrap response in a singleton tuple
  259.             response = (response,)
  260.             response = xmlrpclib.dumps(response, methodresponse=1,
  261.                                        allow_none=self.allow_none, encoding=self.encoding)
  262.         except Fault, fault:
  263.             response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
  264.                                        encoding=self.encoding)
  265.         except:
  266.             # report exception back to server
  267.             response = xmlrpclib.dumps(
  268.                 xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)),
  269.                 encoding=self.encoding, allow_none=self.allow_none,
  270.                 )
  271.  
  272.         return response
  273.  
  274.     def system_listMethods(self):
  275.         """system.listMethods() => ['add', 'subtract', 'multiple']
  276.  
  277.         Returns a list of the methods supported by the server."""
  278.  
  279.         methods = self.funcs.keys()
  280.         if self.instance is not None:
  281.             # Instance can implement _listMethod to return a list of
  282.             # methods
  283.             if hasattr(self.instance, '_listMethods'):
  284.                 methods = remove_duplicates(
  285.                         methods + self.instance._listMethods()
  286.                     )
  287.             # if the instance has a _dispatch method then we
  288.             # don't have enough information to provide a list
  289.             # of methods
  290.             elif not hasattr(self.instance, '_dispatch'):
  291.                 methods = remove_duplicates(
  292.                         methods + list_public_methods(self.instance)
  293.                     )
  294.         methods.sort()
  295.         return methods
  296.  
  297.     def system_methodSignature(self, method_name):
  298.         """system.methodSignature('add') => [double, int, int]
  299.  
  300.         Returns a list describing the signature of the method. In the
  301.         above example, the add method takes two integers as arguments
  302.         and returns a double result.
  303.  
  304.         This server does NOT support system.methodSignature."""
  305.  
  306.         # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
  307.  
  308.         return 'signatures not supported'
  309.  
  310.     def system_methodHelp(self, method_name):
  311.         """system.methodHelp('add') => "Adds two integers together"
  312.  
  313.         Returns a string containing documentation for the specified method."""
  314.  
  315.         method = None
  316.         if self.funcs.has_key(method_name):
  317.             method = self.funcs[method_name]
  318.         elif self.instance is not None:
  319.             # Instance can implement _methodHelp to return help for a method
  320.             if hasattr(self.instance, '_methodHelp'):
  321.                 return self.instance._methodHelp(method_name)
  322.             # if the instance has a _dispatch method then we
  323.             # don't have enough information to provide help
  324.             elif not hasattr(self.instance, '_dispatch'):
  325.                 try:
  326.                     method = resolve_dotted_attribute(
  327.                                 self.instance,
  328.                                 method_name,
  329.                                 self.allow_dotted_names
  330.                                 )
  331.                 except AttributeError:
  332.                     pass
  333.  
  334.         # Note that we aren't checking that the method actually
  335.         # be a callable object of some kind
  336.         if method is None:
  337.             return ""
  338.         else:
  339.             import pydoc
  340.             return pydoc.getdoc(method)
  341.  
  342.     def system_multicall(self, call_list):
  343.         """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
  344. [[4], ...]
  345.  
  346.         Allows the caller to package multiple XML-RPC calls into a single
  347.         request.
  348.  
  349.         See http://www.xmlrpc.com/discuss/msgReader$1208
  350.         """
  351.  
  352.         results = []
  353.         for call in call_list:
  354.             method_name = call['methodName']
  355.             params = call['params']
  356.  
  357.             try:
  358.                 # XXX A marshalling error in any response will fail the entire
  359.                 # multicall. If someone cares they should fix this.
  360.                 results.append([self._dispatch(method_name, params)])
  361.             except Fault, fault:
  362.                 results.append(
  363.                     {'faultCode' : fault.faultCode,
  364.                      'faultString' : fault.faultString}
  365.                     )
  366.             except:
  367.                 results.append(
  368.                     {'faultCode' : 1,
  369.                      'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
  370.                     )
  371.         return results
  372.  
  373.     def _dispatch(self, method, params):
  374.         """Dispatches the XML-RPC method.
  375.  
  376.         XML-RPC calls are forwarded to a registered function that
  377.         matches the called XML-RPC method name. If no such function
  378.         exists then the call is forwarded to the registered instance,
  379.         if available.
  380.  
  381.         If the registered instance has a _dispatch method then that
  382.         method will be called with the name of the XML-RPC method and
  383.         its parameters as a tuple
  384.         e.g. instance._dispatch('add',(2,3))
  385.  
  386.         If the registered instance does not have a _dispatch method
  387.         then the instance will be searched to find a matching method
  388.         and, if found, will be called.
  389.  
  390.         Methods beginning with an '_' are considered private and will
  391.         not be called.
  392.         """
  393.  
  394.         func = None
  395.         try:
  396.             # check to see if a matching function has been registered
  397.             func = self.funcs[method]
  398.         except KeyError:
  399.             if self.instance is not None:
  400.                 # check for a _dispatch method
  401.                 if hasattr(self.instance, '_dispatch'):
  402.                     return self.instance._dispatch(method, params)
  403.                 else:
  404.                     # call instance method directly
  405.                     try:
  406.                         func = resolve_dotted_attribute(
  407.                             self.instance,
  408.                             method,
  409.                             self.allow_dotted_names
  410.                             )
  411.                     except AttributeError:
  412.                         pass
  413.  
  414.         if func is not None:
  415.             return func(*params)
  416.         else:
  417.             raise Exception('method "%s" is not supported' % method)
  418.  
  419. class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  420.     """Simple XML-RPC request handler class.
  421.  
  422.     Handles all HTTP POST requests and attempts to decode them as
  423.     XML-RPC requests.
  424.     """
  425.  
  426.     # Class attribute listing the accessible path components;
  427.     # paths not on this list will result in a 404 error.
  428.     rpc_paths = ('/', '/RPC2')
  429.  
  430.     def is_rpc_path_valid(self):
  431.         if self.rpc_paths:
  432.             return self.path in self.rpc_paths
  433.         else:
  434.             # If .rpc_paths is empty, just assume all paths are legal
  435.             return True
  436.  
  437.     def do_POST(self):
  438.         """Handles the HTTP POST request.
  439.  
  440.         Attempts to interpret all HTTP POST requests as XML-RPC calls,
  441.         which are forwarded to the server's _dispatch method for handling.
  442.         """
  443.  
  444.         # Check that the path is legal
  445.         if not self.is_rpc_path_valid():
  446.             self.report_404()
  447.             return
  448.  
  449.         try:
  450.             # Get arguments by reading body of request.
  451.             # We read this in chunks to avoid straining
  452.             # socket.read(); around the 10 or 15Mb mark, some platforms
  453.             # begin to have problems (bug #792570).
  454.             max_chunk_size = 10*1024*1024
  455.             size_remaining = int(self.headers["content-length"])
  456.             L = []
  457.             while size_remaining:
  458.                 chunk_size = min(size_remaining, max_chunk_size)
  459.                 L.append(self.rfile.read(chunk_size))
  460.                 size_remaining -= len(L[-1])
  461.             data = ''.join(L)
  462.  
  463.             # In previous versions of SimpleXMLRPCServer, _dispatch
  464.             # could be overridden in this class, instead of in
  465.             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
  466.             # check to see if a subclass implements _dispatch and dispatch
  467.             # using that method if present.
  468.             response = self.server._marshaled_dispatch(
  469.                     data, getattr(self, '_dispatch', None)
  470.                 )
  471.         except: # This should only happen if the module is buggy
  472.             # internal error, report as HTTP server error
  473.             self.send_response(500)
  474.             self.end_headers()
  475.         else:
  476.             # got a valid XML RPC response
  477.             self.send_response(200)
  478.             self.send_header("Content-type", "text/xml")
  479.             self.send_header("Content-length", str(len(response)))
  480.             self.end_headers()
  481.             self.wfile.write(response)
  482.  
  483.             # shut down the connection
  484.             self.wfile.flush()
  485.             self.connection.shutdown(1)
  486.  
  487.     def report_404 (self):
  488.             # Report a 404 error
  489.         self.send_response(404)
  490.         response = 'No such page'
  491.         self.send_header("Content-type", "text/plain")
  492.         self.send_header("Content-length", str(len(response)))
  493.         self.end_headers()
  494.         self.wfile.write(response)
  495.         # shut down the connection
  496.         self.wfile.flush()
  497.         self.connection.shutdown(1)
  498.  
  499.     def log_request(self, code='-', size='-'):
  500.         """Selectively log an accepted request."""
  501.  
  502.         if self.server.logRequests:
  503.             BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
  504.  
  505. class SimpleXMLRPCServer(SocketServer.TCPServer,
  506.                          SimpleXMLRPCDispatcher):
  507.     """Simple XML-RPC server.
  508.  
  509.     Simple XML-RPC server that allows functions and a single instance
  510.     to be installed to handle requests. The default implementation
  511.     attempts to dispatch XML-RPC calls to the functions or instance
  512.     installed in the server. Override the _dispatch method inhereted
  513.     from SimpleXMLRPCDispatcher to change this behavior.
  514.     """
  515.  
  516.     allow_reuse_address = True
  517.  
  518.     def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
  519.                  logRequests=True, allow_none=False, encoding=None):
  520.         self.logRequests = logRequests
  521.  
  522.         SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
  523.         SocketServer.TCPServer.__init__(self, addr, requestHandler)
  524.  
  525.         # [Bug #1222790] If possible, set close-on-exec flag; if a
  526.         # method spawns a subprocess, the subprocess shouldn't have
  527.         # the listening socket open.
  528.         if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
  529.             flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
  530.             flags |= fcntl.FD_CLOEXEC
  531.             fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
  532.  
  533. class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
  534.     """Simple handler for XML-RPC data passed through CGI."""
  535.  
  536.     def __init__(self, allow_none=False, encoding=None):
  537.         SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
  538.  
  539.     def handle_xmlrpc(self, request_text):
  540.         """Handle a single XML-RPC request"""
  541.  
  542.         response = self._marshaled_dispatch(request_text)
  543.  
  544.         print 'Content-Type: text/xml'
  545.         print 'Content-Length: %d' % len(response)
  546.         print
  547.         sys.stdout.write(response)
  548.  
  549.     def handle_get(self):
  550.         """Handle a single HTTP GET request.
  551.  
  552.         Default implementation indicates an error because
  553.         XML-RPC uses the POST method.
  554.         """
  555.  
  556.         code = 400
  557.         message, explain = \
  558.                  BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
  559.  
  560.         response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
  561.             {
  562.              'code' : code,
  563.              'message' : message,
  564.              'explain' : explain
  565.             }
  566.         print 'Status: %d %s' % (code, message)
  567.         print 'Content-Type: text/html'
  568.         print 'Content-Length: %d' % len(response)
  569.         print
  570.         sys.stdout.write(response)
  571.  
  572.     def handle_request(self, request_text = None):
  573.         """Handle a single XML-RPC request passed through a CGI post method.
  574.  
  575.         If no XML data is given then it is read from stdin. The resulting
  576.         XML-RPC response is printed to stdout along with the correct HTTP
  577.         headers.
  578.         """
  579.  
  580.         if request_text is None and \
  581.             os.environ.get('REQUEST_METHOD', None) == 'GET':
  582.             self.handle_get()
  583.         else:
  584.             # POST data is normally available through stdin
  585.             if request_text is None:
  586.                 request_text = sys.stdin.read()
  587.  
  588.             self.handle_xmlrpc(request_text)
  589.  
  590. if __name__ == '__main__':
  591.     print 'Running XML-RPC server on port 8000'
  592.     server = SimpleXMLRPCServer(("localhost", 8000))
  593.     server.register_function(pow)
  594.     server.register_function(lambda x,y: x+y, 'add')
  595.     server.serve_forever()
  596.